Writing commands
Commands should be in strict mode, or at least, should extend the Command helper module (root/util/helpers/Command.js
).
While technically it shouldn't be needed, the command could even be a simple object, it is more for consistency across all the commands
The helpers.html
and modules.html
document all the helpers and modules files that you can use within the command, note that pretty much all of these
can be accessed via the client
, as they are added as property of the client
. To see which ones are added as property and in what state, check ../util/index.js
For a better view of how a command should look like, check the example
Tables of content
help object
This object purpose is for use in the help command, and to retrieve the commands across the structure
Property | Data Type | Description |
---|---|---|
name | string |
The name of the command, this must be the same as the file name |
category | string |
The category of the command |
usage | string |
The syntax of the command, note that all instances of {prefix} will be replaced by the effective prefix in the help |
description | string |
The description of the command, like usage, instances of {prefix} will be replaced by the actual prefix |
externalDoc | string |
A link to an external documentation page, optional |
params | object |
A parameters object, optional |
help parameters
This object is somewhat particular, as in, properties names aren't predefined, they should be the name of the parameter, and their value should be either a simple string describing the parameter, or the following object. For a better view of how it should look like, check the examples
Property | Data Type | Description |
---|---|---|
description | string |
The description of the parameter |
mandatoryValue | boolean |
Whether or not a value is mandatory for this parameter |
values | array<parameter value> |
An array of parameter value objects |
Note that neither how the parameter and value are parsed matter (you should describe how a value is parsed in the description though), as this object is just for informative use, and will only be used in the help command
parameter value
Property | Data Type | Description |
---|---|---|
name | string |
The name of the value, or something like <value> if the value isn't predefined |
description | string |
Description of what the value means/does |
conf object
This define most of the command characteristics and will directly impact how the command handler behave with it
Property | Data Type | Description |
---|---|---|
requireDB | boolean |
Whether or not the command use the database, if true, the command won't be loaded when the bot is launched without database, and will be dynamically disabled if the connection is lost with the database at some point in the run (and re-enabled once the connection is back) |
disabled | boolean OR string |
Whether the command is disabled, if false, the command is called as expected, otherwise, the value is expected to be a string and the command handler won't run the command. It will instead state to the user that the command is disabled, and print the value as the reason |
aliases | array[string] |
An array of aliases to this command |
requirePerms | array[string] |
An array of permissions (like manageMessages ) that the bot needs for the command to work perfectly, the command won't be triggered if the bot hasn't one of the permissions in the array |
guildOnly | boolean |
Whether this command can only be used in a guild |
ownerOnly | boolean |
Whether this command can only be used by the owner set in the config file |
expectedArgs | array<expected arg> |
An array of arguments the command expect, if you set it, whenever a user trigger the command without arguments, the command handler will query the user for each expected argument |
cooldownWeight | number |
The "weight" of the command, if not specified, this will use the default weight set in the config |
require | array |
An array of API keys key set in the config or/and packages names the command needs |
guildOwnerOnly | boolean |
Whether or not only the owner of the guild can use this command |
Note that the expectedArgs
property is extremely powerful and will affect the arguments with which the run function is called
expected arg
Property | Data Type | Description |
---|---|---|
description | string |
Description that will be used when querying the user. This is the only mandatory property |
condition | function |
A function that will be called with the three client , message and args parameters, where client is the client, message the message and args an array of arguments that have already been prompted to the user. If the function resolve to false , this argument won't be prompted to the user. This function can be async and return a promise, it will in that case be awaited |
possibleValues | array<possible arg value> |
An array of possible arg value objects, which represent the values the command handler should expect |
validate | function |
A custom manual validation function, which will be called with client , message and arg . arg being the argument the user just gave, and message the message that triggered the command in the first place |
possible arg value
Property | Data Type | Description |
---|---|---|
name | string |
The name of the value, it defines what the command handler should expect, if the user input does not match with the name, they will be prompted again. To accept any value, this should be * |
interpretAs | string |
This is especially helpful when the syntax of the command isn't how you would humanly prompt a user, this define what exactly will be pushed in the args array, note that {value} will be replaced by value. If false , it won't be pushed into the args array |
extra
This property is entirely optional, it may be of whatever data type, it is just if you want to have other properties for use by the command itself, they should be
under <Command>.extra
so they're easily accessible and to be consistent with the other commands which do that
run function
This function must return a promise
This is the function where the main command code should be, and the one called by the command handler. The command handler will call it with the following arguments:
Property | Data Type | Description |
---|---|---|
client | * |
The client class |
message | * |
The eris message |
args | array[string] |
An array of arguments, see the args parameter |
guildEntry | object |
This guild database entry, may be undefined unless conf.requireDB is true |
userEntry | object |
The user who ran the command database entry, may be undefined unless conf.requireDB is true |
The args parameter
While the args
parameter are by default directly parsed from the message content, each values of the array are words of the message separated by at least a space
(prefix and command are not included), this is not always the case. If conf.expectedArgs
is set and a user trigger the command without specifying any argument, the args
array will no longer simply be the message content parsed. Each argument in the conf.expectedArgs
will be prompted to the user, and the args
array
will be filled with the user replies.
To see this behavior in action, and a full scope of how powerful it can be, you may want to check out the reload command code (see the second example) and try running the reload command without any argument, or even better, the experience command code, which show-off ways to do a lot of things, like dynamically transforming the args through the arguments conditions
Examples
For a very basic command, here's an example:
'use strict';
const Command = require('../../util/helpers/modules/Command');
class Ping extends Command {
constructor() {
super();
this.help = {
name: 'ping',
category: 'generic',
description: 'pong',
usage: '{prefix}ping'
};
this.conf = {
requireDB: false,
disabled: false,
aliases: [],
requirePerms: [],
guildOnly: false,
ownerOnly: false,
expectedArgs: []
};
}
async run(client, message, args, guildEntry, userEntry) {
//...
}
}
module.exports = new Ping();
For a more complicated command that takes full advantage of the command handler and it's possibilities:
'use strict';
const Command = require('../../util/helpers/modules/Command');
const { inspect } = require('util');
class Reload extends Command {
constructor() {
super();
this.help = {
name: 'reload',
category: 'admin',
description: 'Reload a module - This command use a command-line like syntax for its parameters, as in, parameters looks like `--<parameter_name>`. Parameters can have a value, the syntax for specifying a value for a parameter is `--<parameter_name>=<value>`\n\nExample: `reload ./module.js --module --bindToClient=moduleBaguette --instantiate`\nThe above example reload the file `module.js` at the root of this command\'s folder, instantiate it without additional parameters and add it as a propriety of the client class under the name `moduleBaguette`',
usage: '{prefix}reload <file_path> <params>',
params: {
'--event': 'Specify that the file you want to reload is an event listener',
'--command': 'Specify that the file you want to reload is a command, unless the command isn\'t added yet, a path is usually not needed and the command name can be provided instead',
'--module': 'Specify that the file you want to reload is a module, permit the use of the `--bindToClient` and `--instantiate` parameters',
'--bindToClient': {
description: 'Specify that the file should be added as a property of the client class',
mandatoryValue: false,
values: [{
name: '<name>',
description: 'Specify the name under which the file should be added as a property of the client class'
}]
},
'--instantiate': {
description: 'This specify that the command should expect a non-instantiated class that should be instantiated',
mandatoryValue: true,
values: [{
name: 'client',
description: 'Specify that the class should be instantiated with the client'
}, {
name: 'bot',
description: 'Specify that the class should be instantiated with the bot instance'
}]
}
}
};
this.conf = {
requireDB: false,
disabled: false,
aliases: [],
requirePerms: [],
guildOnly: false,
ownerOnly: false,
expectedArgs: [{
description: 'Please specify the path of the file you want to reload/add, or, if a command that is already loaded, the name of the command',
}, {
description: 'Please specify the type of the file you want to reload, can be either `event`, `command` or `module`',
possibleValues: [{
name: 'command',
interpretAs: '--command'
}, {
name: 'event',
interpretAs: '--event'
}, {
name: 'module',
interpretAs: '--module'
}]
}, {
//Conditional branch
description: 'Please specify if a non-instantiated class should be expected from this module, and with what it should be instantiated. Can be either `bot`, `client` or `no` to not instantiate it',
condition: (client, message, args) => args.includes('--module'),
possibleValues: [{
name: 'bot',
interpretAs: '--instantiate=bot',
}, {
name: 'client',
interpretAs: '--instantiate=client'
}, {
name: 'no',
interpretAs: false
}]
}, {
//Conditional branch
description: 'Please specify whether the module should be added as a property of the client class, can be either `yes`, `<name>` or `no`. Where `<name>` is the name under which the property should be added, if `yes`, the file name will be used',
condition: (client, message, args) => args.includes('--module'),
possibleValues: [{
name: 'yes',
interpretAs: '--bindtoclient',
}, {
name: '*',
interpretAs: '--bindtoclient={value}'
}, {
name: 'no',
interpretAs: false
}]
}]
};
}
async run(client, message, args, guildEntry, userEntry) {
//...
}
}
module.exports = new Reload();
Helpful methods
As you can see in the examples, commands should extend the Command
class. While it's not an absolute necessity, it gives access to the following helpful methods through
the this
keyword
Command
Kind: global class
- Command
- new Command()
- .parseCommand(message, client) ⇒
Promise.<object>
- .clientHasPermissions(message, client, permissions, [channel]) ⇒
boolean
|array
- .hasChannelOverwrite(channel, member, permission) ⇒
boolean
|PermissionOverwrite
- .getUserFromText(options) ⇒
Promise.<User>
- .getRoleFromText(options) ⇒
Promise.<Role>
- .getChannelFromText(options) ⇒
object
|boolean
- .queryMissingArgs(client, message, command) ⇒
Promise.<Array>
- .resolveUser(client, userResolvable) ⇒
extendedUser
- .getHighestRole(member, guild) ⇒
*
new Command()
Provide some utility methods to parse the args of a message, check the required permissions...
command.parseCommand(message, client) ⇒ Promise.<object>
As it calls the database to check for a custom prefix, the method is asynchronous and may be awaited
Kind: instance method of Command
Returns: Promise.<object>
- - The command object, or undefined if the message is not prefixed or the command does not exist
Param | Type | Description |
---|---|---|
message | object |
The message object to parse the command from |
client | object |
The client instance |
command.clientHasPermissions(message, client, permissions, [channel]) ⇒ boolean
| array
This is a deep check and the channels wide permissions will be checked too
Kind: instance method of Command
Returns: boolean
| array
- - An array of permissions the bot miss, or true if the bot has all the permissions needed, sendMessages permission is also returned if missing
Param | Type | Default | Description |
---|---|---|---|
message | object |
The message that triggered the command | |
client | object |
The client instance | |
permissions | array |
An array of permissions to check for | |
[channel] | object |
message.channel |
Optional, a specific channel to check perms for (to check if the bot can connect to a VC for example) |
command.hasChannelOverwrite(channel, member, permission) ⇒ boolean
| PermissionOverwrite
It takes into account the roles of the member, their position and the member itself to return the overwrite which actually is effective
Kind: instance method of Command
Returns: boolean
| PermissionOverwrite
- - The permission overwrite overwriting the specified permission, or false if none exist
Param | Type | Description |
---|---|---|
channel | object |
The channel to check permissions overwrites in |
member | object |
The member object to check permissions overwrites for |
permission | string |
The permission to search channel overwrites for |
command.getUserFromText(options) ⇒ Promise.<User>
Try to resolve a user with IDs, names, partial usernames or mentions
Kind: instance method of Command
Returns: Promise.<User>
- The resolved role, or false if none could be resolved
Param | Type | Default | Description |
---|---|---|---|
options | object |
An object of options | |
options.message | object |
The message from which to get the roles from | |
options.client | object |
The client instance | |
[options.text] | string |
"message.content" |
The text from which roles should be resolved, if none provided, it will use the message content |
command.getRoleFromText(options) ⇒ Promise.<Role>
Try to resolve a role with IDs or names
Kind: instance method of Command
Returns: Promise.<Role>
- The resolved role, or false if none could be resolved
Param | Type | Default | Description |
---|---|---|---|
options | object |
An object of options | |
options.message | object |
The message from which to get the roles from | |
options.client | object |
The client instance | |
[options.text] | string |
"message.content" |
The text from which roles should be resolved, if none provided, it will use the message content |
command.getChannelFromText(options) ⇒ object
| boolean
Kind: instance method of Command
Returns: object
| boolean
- The channel object, or false if none found
Param | Type | Default | Description |
---|---|---|---|
options | object |
An object of options | |
options.client | object |
The client instance | |
options.message | object |
The message | |
[options.text] | string |
"message.content" |
The text to resolve a channel from |
[options.textual] | boolean |
true |
Whether the channel to resolve is a text channel or a voice channel |
command.queryMissingArgs(client, message, command) ⇒ Promise.<Array>
Query to the user the arguments that they forgot to specify
Kind: instance method of Command
Returns: Promise.<Array>
- An array of arguments
Param | Type | Description |
---|---|---|
client | * |
The client instance |
message | * |
The message that triggered the command |
command | * |
The command that the user is trying to run |
command.resolveUser(client, userResolvable) ⇒ extendedUser
Note that if the user is not found, only username, discriminator and tag are guaranteed (set to unknown)
Kind: instance method of Command
Returns: extendedUser
- returns an extended user object
Param | Type | Description |
---|---|---|
client | * |
The client instance |
userResolvable | * |
A user resolvable, can be an ID, a username#discriminator pattern or a user object |
command.getHighestRole(member, guild) ⇒ *
Get the highest role of the specified member and returns it
Kind: instance method of Command
Returns: *
- The highest role of the user
Param | Type | Description |
---|---|---|
member | object | string |
The member object or their ID |
guild | * |
The guild object |